package com.onpositive.realmview.queryeditor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.Viewer;

import com.onpositive.realmview.IRealmView;
import com.onpositive.realmview.model.IViewGrouping;
import com.onpositive.semantic.model.api.property.DefaultPropertyMetadata;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.property.IPropertyMetaData;
import com.onpositive.semantic.model.api.property.IPropertyProvider;
import com.onpositive.semantic.model.api.property.adapters.ITextLabelProvider;

public class GroupingContentProvider implements ITreeContentProvider {

	IRealmView realmView;
	Object[] contents;
	private Collection<IViewGrouping> groupings;
	private IViewGrouping first;
	private IViewGrouping second;

	public GroupNode resolve(String memento) {
		String[] split = memento.split(":");
		GroupNode parent = null;
		StructuredViewer viewer2 = (StructuredViewer) viewer;
		ILabelProvider labelProvider = (ILabelProvider) viewer2
				.getLabelProvider();
		for (String s : split) {
			int indexOf = s.indexOf('.');
			String groupingId = s.substring(0, indexOf);
			String value = s.substring(indexOf + 1);
			IViewGrouping grouping = realmView.getParent().getQueryProvider()
					.getGrouping(groupingId);
			if (grouping == null) {
				return parent;
			}
			Object[] tryGroup = tryGroup(parent, grouping, contents);
			for (Object o : tryGroup) {
				if (o instanceof GroupNode) {
					GroupNode m = (GroupNode) o;
					String text = m.value != null ? m.value.toString() : "";
					if (text != null && text.equals(value)) {
						parent = (GroupNode) o;
					}
				}
			}
		}
		return parent;
	}

	public Object[] getChildren(Object parentElement) {
		if (parentElement instanceof GroupNode) {
			GroupNode groupNode = (GroupNode) parentElement;
			Object[] children = groupNode.getChildren();
			if (children.length > 100 || getChildGrouping(groupNode) != null) {
				return group(groupNode, children);
			}
			if (groupNode.group.equals(first) && second != null
					&& !second.equals(first)) {
				if (second != null) {
					if (children.length > 0) {
						return group(groupNode, children);
					}
				}
			}
			return children;
		}
		return null;
	}

	private Object[] group(GroupNode vv, Object[] children) {

		IViewGrouping grouping = getChildGrouping(vv);
		if (vv != null && grouping != null) // When we have node-specific
											// grouping settings
		{
			Object[] tryGroup = tryGroup(vv, grouping, children);
			if (tryGroup != null && tryGroup.length < 200) {
				return tryGroup;
			}
		} else {
			if (vv == null && first != null) {
				Object[] tryGroup = tryGroup(vv, first, children);
				if (tryGroup.length < 200) {
					return tryGroup;
				}
			}
			if (vv != null) {
				if (second != null && vv.group.equals(first)
						&& !first.equals(second)) {
					Object[] tryGroup = tryGroup(vv, second, children);
					if (tryGroup.length < 200) {
						return tryGroup;
					}
				}
			}
		}
		Object[] laObjects = null;
		for (IViewGrouping v : groupings) {
			if (vv == null || !vv.isUsed(v)) {
				Object[] tryGroup = tryGroup(vv, v, children);
				// System.out.println(v.getId());
				if (tryGroup.length > 3 && tryGroup.length < 200) {
					return tryGroup;
				} else {
					if (tryGroup.length > 200) {
						laObjects = tryGroup;
					}
					// System.out.println(v.getId()+" rejected:"+tryGroup.length);
				}
			}
		}
		if (laObjects != null && laObjects.length < children.length) {
			return laObjects;
		}
		return children;
	}

	protected IViewGrouping getChildGrouping(GroupNode node) {
		if (node == null)
			return null;
		String groupingString = realmView.getSettings().get(
				node.getLongString());
		for (Iterator<IViewGrouping> iterator = groupings.iterator(); iterator
				.hasNext();) {
			IViewGrouping grouping = (IViewGrouping) iterator.next();
			if (grouping.getId().equals(groupingString))
				return grouping;
		}
		return node.getChildrenGrouping();
	}

	//TODO hierarcy support for Object documents
	public Object getParent(Object element) {
		if (element instanceof GroupNode) {
			GroupNode n = (GroupNode) element;
			if (n.parent != null) {
				return n.parent;
			}
			return realmView;
		}
		return null;
	}

	public boolean hasChildren(Object element) {
		return true;
	}

	public int version = 0;
	private Viewer viewer;
	IPropertyProvider provider;
	private Object[] groups;
	private boolean standalone;

	public void setStandalone(boolean standalone) {
		this.standalone = standalone;
	}

	@SuppressWarnings("unchecked")
	public Object[] getElements(Object inputElement) {
		if (inputElement instanceof GroupNode) {
			return getChildren(inputElement);
		}
		realmView = (IRealmView) inputElement;
		provider = realmView.getParent().getQueryProvider().provider();
		version++;
		groupings = new ArrayList<IViewGrouping>(realmView.getParent()
				.getQueryProvider().getGroupings());
		Collections.sort((List<Comparable>) (Collection) groupings);
		resetContents(realmView);
		groups = group(null, contents);
		return groups;
	}

	public void resetContents(IRealmView realmView) {
		contents = realmView.getContents();
	}

	@SuppressWarnings("unchecked")
	private Object[] tryGroup(GroupNode parent, IViewGrouping grouping,
			Object[] contents) {
		HashMap<Object, GroupNode> v = new HashMap<Object, GroupNode>();
		IProperty property = provider.getProperty(null, grouping.getId());
		if (property == null) {
			return null;
		}

		IPropertyMetaData pmd = property.getPropertyMetaData();
		boolean useLabelAsValue = false;
		ITextLabelProvider itl = null;

		Object mdval = ((DefaultPropertyMetadata) pmd)
				.get(DefaultPropertyMetadata.USE_LABEL_AS_VALUE);
		if (mdval instanceof Boolean) {
			useLabelAsValue = ((Boolean) mdval).booleanValue();
			if (useLabelAsValue) {
				itl = pmd.getAdapter(ITextLabelProvider.class);
			}
		}

		for (Object object : contents) {
			String groupName = "";
//			IRichDocument c = (IRichDocument) o;
			boolean flag = useLabelAsValue;
//			if (c instanceof AbstractRichDocument) {
//				flag = useLabelAsValue
//						&& ((AbstractRichDocument) c)
//								.usingLabelsInsteadOFValuesIsPossible();
//			}

			Object propertyValue = property.getValue(object);
			/* Object modifiedValue = propertyValue; */
			IPropertyMetaData meta = property.getPropertyMetaData();

			if (flag) {

				if (itl != null) {
					propertyValue = itl.getText(propertyValue);
				}
			}
			if (propertyValue == null) {
				propertyValue = "";

				if (pmd instanceof DefaultPropertyMetadata) {
					DefaultPropertyMetadata dpmd = (DefaultPropertyMetadata) pmd;
					Object dv = dpmd
							.get(DefaultPropertyMetadata.DEFAULT_PROPERTY_VALUE);
					if (dv instanceof String) {
						groupName = dv.toString();
					}
				}

			}
			if (propertyValue instanceof Collection<?>) {
				Collection<Object> m = (Collection<Object>) propertyValue;
				for (Object pro : m) {
					GroupNode groupNode = v.get(pro);
					if (groupNode == null) {
						groupNode = new GroupNode(this, parent, grouping, pro,
								"", object);
						v.put(pro, groupNode);
					}
					groupNode.append(object);
				}
				if (m.size() == 0) {
					propertyValue = "";
					GroupNode groupNode = v.get(propertyValue);// propertyValue
					if (groupNode == null) {
						groupNode = new GroupNode(this, parent, grouping,
								propertyValue, "", object);
						v.put(propertyValue, groupNode);// propertyValue
					}
					groupNode.append(object);
				}
			} else {
				GroupNode groupNode = v.get(propertyValue);// propertyValue
				if (groupNode == null) {
					groupNode = new GroupNode(this, parent, grouping,
							propertyValue, groupName, object);
					v.put(propertyValue, groupNode);// propertyValue
				}
				groupNode.append(object);
			}
		}
		GroupNode[] qq = new GroupNode[v.size()];
		v.values().toArray(qq);
		return qq;
	}

	public void dispose() {
	}

	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		this.viewer = viewer;
	}

	public void setFirstGrouping(IViewGrouping p) {
		this.first = p;
	}

	public void setSecondGrouping(IViewGrouping p) {
		this.second = p;
	}

	public void refreshNode() {
		version++;
		contents = realmView.getContents();
	}

	public IRealmView getView() {
		return realmView;
	}

	public TreePath map(TreePath p) {
		Object lastSegment = p.getLastSegment();
//		if (lastSegment instanceof IRichDocument) {
//			lastSegment = realmView.copyIfExists(lastSegment);
//		}
		if (lastSegment instanceof GroupNode) {
			return p;
		} else {
			if (p.getFirstSegment() instanceof GroupNode) {
				GroupNode g = (GroupNode) p.getFirstSegment();
				g = mapNode(g, groups);
				if (g != null) {
					TreePath z = determinePath(lastSegment, g, removeOne(p));
					if (z != null) {
						return reverse(z);
					}
				}

			}
			for (Object q : groups) {
				TreePath z = determinePath(lastSegment, q, removeOne(p));
				if (z != null) {
					return reverse(z);
				}
			}
		}

		return null;
	}

	private GroupNode mapNode(GroupNode g, Object[] groups2) {
		for (Object q : groups2) {
			if (g.isSame(q)) {
				return (GroupNode) q;
			}
		}
		return null;
	}

	TreePath removeOne(TreePath z) {

		int sx = z.getSegmentCount();
		if (sx == 0) {
			return z;
		}
		Object[] reverse = new Object[sx - 1];
		for (int a = 1; a < sx; a++) {
			reverse[a - 1] = z.getSegment(a);
		}
		return new TreePath(reverse);
	}

	private TreePath reverse(TreePath z) {
		int sx = z.getSegmentCount();
		Object[] reverse = new Object[sx];
		for (int a = 0; a < sx; a++) {
			reverse[sx - a - 1] = z.getSegment(a);
		}
		return new TreePath(reverse);
	}

	private TreePath determinePath(Object lastSegment, Object q, TreePath old) {

		if (lastSegment.equals(q)) {
			return new TreePath(new Object[] { lastSegment });
		}
		// worsest case
		if (q instanceof GroupNode) {
			GroupNode n = (GroupNode) q;
//			IRichDocument c = (IRichDocument) lastSegment;
			Object propertyValue = n.p.getValue(lastSegment);
			if (propertyValue == null) {
				propertyValue = "";
			}
			if (propertyValue instanceof Collection) {
				Collection ca = (Collection) propertyValue;
				if (ca.contains(n.value)) {
					Object[] children = getChildren(n);
					if (old.getSegmentCount() > 0) {
						Object firstSegment = old.getFirstSegment();
						{
							if (firstSegment instanceof GroupNode) {
								GroupNode nq = mapNode(
										(GroupNode) firstSegment, children);
								TreePath determinePath = determinePath(
										lastSegment, nq, removeOne(old));
								if (determinePath != null) {
									return determinePath.createChildPath(n);
								}
							}
						}
					}
					for (Object l : children) {

						TreePath determinePath = determinePath(lastSegment, l,
								removeOne(old));
						if (determinePath != null) {
							return determinePath.createChildPath(n);
						}
					}
					// this node;
				}
			} else if (propertyValue.equals(n.value)) {
				Object[] children = getChildren(n);
				if (old.getSegmentCount() > 0) {
					Object firstSegment = old.getFirstSegment();
					{
						if (firstSegment instanceof GroupNode) {
							GroupNode nq = mapNode((GroupNode) firstSegment,
									children);
							TreePath determinePath = determinePath(lastSegment,
									nq, removeOne(old));
							if (determinePath != null) {
								return determinePath.createChildPath(n);
							}
						}
					}
				}
				for (Object l : children) {
					TreePath determinePath = determinePath(lastSegment, l,
							removeOne(old));
					if (determinePath != null) {
						return determinePath.createChildPath(n);
					}
				}
				// this node;
			}
		}
		return null;
	}

	public boolean isStandAlone() {
		return standalone;
	}

}
